/*
Digital Image Processing - Final Project
9 December 2011

Joseph Dombrowski
Josh Dixon
Junmo An
*/

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <stdio.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <math.h>
#include "image.h"
#include "line.h"
#include <time.h>

#define _LOADING 0
#define _EDGES 1
#define _OPTIMIZING 2
#define _DONE 3

using namespace std;

// Member variables
vector<Image> Images;
Image corrected;
Image sidebar = Image();
int i;
bool imgOn[3] = {false,false,false};

// Member functions
void showHideWindow(int theEvent, int x, int y, int flags, void* param);
void setStatus(int status);

// Main
int main(int argc, char *argv[])
{
	clock_t start;
	clock_t end;
	
	// If image files are dropped onto the exe, use them
	if(argc > 1)
		for (int i=1; i<argc; i++)
			Images.push_back(Image(argv[i]));
	// Otherwise, use default image
	else
		Images.push_back(Image("distort1.png"));

	int numImages = Images.size();
	

	// Code logic executed for each image
	for(i=0; i<numImages; i++)
	{
		start = clock();

		// Initialize the sidebar
		sidebar = Image("sidebar.png","Information");
		setStatus(_LOADING);
		sidebar.drawLabel(Images[i].name(),point(24,37),cvScalar(0,0,0,1),250,1.0);
		sidebar.showImage();
		cvSetMouseCallback("Information", showHideWindow);
		
		// Process image
		vector<edge> edges;

		setStatus(_EDGES);				// Update sidebar
		Images[i].detectEdges(edges);	// Find the edges

		setStatus(_OPTIMIZING);			// Update sidebar

		double initDistortion = Images[i].calculateDistortion();
		double thisDistort;
		double minDistort = INT_MAX;

		// Optimization runs until 3 conditions: 
		// 1. Change is less than 10% from last error
		// 2. Current error level is the lowest ever
		// 3. Change is in the correct direction
		// If these conditions are not met, fails after 10 iterations
		for(int j=0; j<10; j++)
		{
			Images[i].minimizeDistortionByAdjustingK();						// Find K
			Images[i].minimizeDistortionByAdjustingCenterOfDistortion();	// Find C
			thisDistort = Images[i].calculateDistortion();					// Recalculate distortion

			if(thisDistort < minDistort)
				minDistort = thisDistort;

			if(initDistortion*0.1 > abs(initDistortion - thisDistort) && initDistortion - thisDistort > 0 && minDistort == thisDistort)
				break;
			else
				initDistortion = thisDistort;
		}

		// Copy the image for output
		corrected = Image(Images[i].copyImg(),"Corrected Image", Images[i].getK(), Images[i].getCenter(), Images[i].getS());
		// Apply correction
		corrected.correctDistortion();
		
		setStatus(_DONE);	// Update sidebar
		end = clock();

		// Update sidebar with image information
		stringstream center, kfactor, sfactor, timeelapsed;
		center << "Center: (" << Images[i].getCenter().x << ", " << Images[i].getCenter().y << ")";
		kfactor << "K: " << Images[i].getK() * 10000000 << " x10^-7";
		sfactor << "S: " << Images[i].getS();
		timeelapsed << "Time: " << double(end - start)/CLOCKS_PER_SEC << " s";

		sidebar.drawLabel(center.str().c_str(),point(24,250),cvScalar(0,0,0,1),250,0.8);
		sidebar.drawLabel(kfactor.str().c_str(),point(24,275),cvScalar(0,0,0,1),250,0.8);
		sidebar.drawLabel(sfactor.str().c_str(),point(24,300),cvScalar(0,0,0,1),250,0.8);
		sidebar.drawLabel(timeelapsed.str().c_str(),point(24,325),cvScalar(0,0,0,1),250,0.8);

		sidebar.drawImgAt(cvLoadImage("off.png"),point(80,379));
		sidebar.drawImgAt(cvLoadImage("off.png"),point(174,379));
		sidebar.drawImgAt(cvLoadImage("off.png"),point(267,379));

		Images[i].showHistogram(sidebar);

		// Show image if set from last time
		if(imgOn[0])
		{
			sidebar.drawImgAt(cvLoadImage("on.png"),point(80,379));
			Images[i].showImage();     // Original image		
		}
		if(imgOn[1])
		{
			sidebar.drawImgAt(cvLoadImage("on.png"),point(174,379));
			corrected.showImage();     // Corrected image
		}
		if(imgOn[2])
		{
			sidebar.drawImgAt(cvLoadImage("on.png"),point(267,379));
			Images[i].showEdges();	   // The original image edges
		}

		// Save computed image
		stringstream filename;
		filename << Images[i].name() << "_corrected.png";
		corrected.saveImage(filename.str());

		cvWaitKey(0);

		Images[i].releaseImage(); // Destroy images before next iteration
		corrected.releaseImage();
	}
	return 0;
}

// Handler for mouse click. Toggles button indicator and window state.
void showHideWindow(int theEvent, int x, int y, int flags, void* param)
{

	if(theEvent == CV_EVENT_LBUTTONDOWN && y > 376 && y < 448)
	{
		int theCase = 0;
		if(x >= 22 && x <= 92)
			theCase = 1;
		else if(x >= 116 && x <= 186)
			theCase = 2;
		else if(x >= 209 && x <= 279)
			theCase = 3;

		switch(theCase)
		{
			case 1: 
				if(!imgOn[0])
				{
					sidebar.drawImgAt(cvLoadImage("on.png"),point(80,379));
					sidebar.showImage();
					Images[i].showImage();
					imgOn[0] = true;
				}
				else
				{
					sidebar.drawImgAt(cvLoadImage("off.png"),point(80,379));
					sidebar.showImage();
					Images[i].hideImage();
					imgOn[0] = false;
				}
				break;
			case 2: 
				if(!imgOn[1])
				{
					sidebar.drawImgAt(cvLoadImage("on.png"),point(174,379));
					sidebar.showImage();
					corrected.showImage();
					imgOn[1] = true;
				}
				else
				{
					sidebar.drawImgAt(cvLoadImage("off.png"),point(174,379));
					sidebar.showImage();
					corrected.hideImage();
					imgOn[1] = false;
				}
				break;
			case 3: 
				if(!imgOn[2])
				{
					sidebar.drawImgAt(cvLoadImage("on.png"),point(267,379));
					sidebar.showImage();
					Images[i].showEdges();
					imgOn[2] = true;
				}
				else
				{
					sidebar.drawImgAt(cvLoadImage("off.png"),point(267,379));
					sidebar.showImage();
					Images[i].hideEdges();
					imgOn[2] = false;
				}
				break;
		}
	}
}

// Updates sidebar with status
void setStatus(int status)
{
	switch(status)
	{
		case 0: 
			sidebar.drawLabel("Loading...",point(10,455),cvScalar(0,0,0,1),300,1.0);
			break;
		case 1: 
			sidebar.drawLabel("Finding Edges...",point(10,455),cvScalar(0,0,0,1),300,1.0);
			break;
		case 2: 
			sidebar.drawLabel("Optimizing...",point(10,455),cvScalar(0,0,0,1),300,1.0);
			break;
		case 3: 
			sidebar.drawLabel("",point(10,455),cvScalar(0,0,0,1),300,1.0);
			break;
	}
}